import wx, wx.stc as stc
import os, sys, time, json, re
import sys, keyword
from .laxerparse import Laxer
sys.path.append('../')

class IDEObject:
    def __init__(self, name='noname', path=None):
        self.saved = False
        self.path = path
        self.name = name

class Code(IDEObject):
    def __init__(self, cont='', name='Code', path=None):
        IDEObject.__init__(self, name, path)
        self.cont = cont
        self.saved = not path is None

style_config = json.loads(
'''{
    "STYLE_DEFAULT":["#000000", "#ffffff", "Courier New", 10, false, false],
    "STYLE_LINENUMBER":[null, "#C0C0C0", null, 12, null, null],
    "STYLE_CONTROLCHAR":[null, null, null, null, null, null],
    "STYLE_BRACELIGHT":["#0000FF", "#FFFF88", null, null, null, null],
    "STYLE_BRACEBAD":["#FF0000", "#FFFF88", null, null, null, null],
    "P_DEFAULT":[null, null, null, null, null, null],
    "P_COMMENTLINE":["#007F00", null, null, null, null, null],
    "P_NUMBER":[null, null, null, null, null, null],
    "P_STRING":["#7F007F", null, null, null, null, null],
    "P_CHARACTER":["#7F007F", null, null, null, null, null],
    "P_WORD":["#00007F", null, null, null, true, null],
    "P_TRIPLE":["#7F0000", null, null, null, null, null],
    "P_TRIPLEDOUBLE":["#000033", "#FFFFE8", null, null, null, null],
    "P_CLASSNAME":["#0000FF", null, null, null, true, null],
    "P_DEFNAME":["#007F7F", null, null, null, true, null],
    "P_OPERATOR":[null, null, null, null, null, null],
    "P_IDENTIFIER":[null, null, null, null, null, null],
    "P_COMMENTBLOCK":["#7F7F7F", null, null, null, null, null],
    "P_STRINGEOL":["#000000", "#E0C0E0", null, null, null, null]
}''')

class CodePad(stc.StyledTextCtrl):
    """EditWindow based on StyledTextCtrl."""    
    def __init__(self, parent, code = ('', 'Code', None)):
        """Create EditWindow instance."""
        stc.StyledTextCtrl.__init__(self, parent, id=-1, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER)
        self.set_code(Code(*code))
        self.__config()
    
        self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
        self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
        self.Bind(wx.stc.EVT_STC_CHARADDED, self.on_text_changed)
        self.Bind(stc.EVT_STC_AUTOCOMP_COMPLETED, self.on_autocomp_finish)
        self.laxer = Laxer()
        self.fixed = False

    def __config(self):
        
        self.SetLexer(stc.STC_LEX_PYTHON)
        self.SetKeyWords(0, ' '.join(keyword.kwlist))

        self.setStyles()
        self.SetViewWhiteSpace(False)
        self.SetTabWidth(4)
        self.SetUseTabs(False)
        # Do we want to automatically pop up command completion options?
        self.autoComplete = True
        self.autoCompleteIncludeMagic = True
        self.autoCompleteIncludeSingle = True
        self.autoCompleteIncludeDouble = True
        self.autoCompleteCaseInsensitive = False
        self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
        self.autoCompleteAutoHide = True
        self.AutoCompSetAutoHide(self.autoCompleteAutoHide)
        #self.AutoCompStops(' .,;:([)]}\'"\\<>%^&+-=*/|`')
        # Do we want to automatically pop up command argument help?
        self.autoCallTip = True
        self.callTipInsert = True
        #self.CallTipSetBackground(FACES['calltipbg'])
        #self.CallTipSetForeground(FACES['calltipfg'])
        self.SetWrapMode(False)
        try:
            self.SetEndAtLastLine(False)
        except AttributeError:
            pass
        self.breakpoints = []
        self.SetupMargin()
        
        self.SetBackSpaceUnIndents(True)
        self.SetWrapIndentMode(stc.STC_WRAPINDENT_FIXED)

    def SetupMargin(self):
        self.SetMarginType(0, stc.STC_MARGIN_NUMBER)
        self.SetMarginWidth(0, 0)
        self.SetMarginType(1, stc.STC_MARGIN_SYMBOL)
        self.SetMarginWidth(1, 22)
        self.SetMarginSensitive(1, True)
        # self.MarkerDefine(0, stc.STC_MARK_ROUNDRECT, "#CCFF00", "RED")
        self.MarkerSetBackground(0, "red")
        self.MarkerDefine(1, stc.STC_MARK_ARROW, "#00FF00", "#00FF00")

    def setStyles(self):
        def dump(cont):
            title = ['fore','back','face','size','bold','italic']
            idx = [i for i in range(6) if not (cont[i] is None or cont[i] is False)]
            return ','.join(['%s:%s'%(title[i], cont[i]) for i in idx])
        self.StyleSetSpec(stc.STC_STYLE_DEFAULT, dump(style_config['STYLE_DEFAULT']))
        self.StyleClearAll()
        self.SetSelForeground(True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT))
        self.SetSelBackground(True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT))
        for i in style_config: self.StyleSetSpec(eval('stc.STC_'+i), dump(style_config[i]))
        
    # get the selection line start, end
    def get_sel_line(self):
        caretPos = self.GetCurrentPos()

        # selection start, end
        sel_start_pos, sel_end_pos = self.GetSelection()

        sel_start_line = self.PositionToXY(sel_start_pos)[2]
        sel_end_line = self.PositionToXY(sel_end_pos-1)[2]
        
        if sel_start_pos == sel_end_pos:
            sel_end_line = sel_start_line
        
        # print('pos2xy', self.PositionToXY(sel_start_pos), self.PositionToXY(sel_end_pos-1))

        return sel_start_line, sel_end_line, sel_start_pos, sel_end_pos

    def OnUpdateUI(self, event):       
        braceAtCaret = -1
        braceOpposite = -1
        charBefore = None
        caretPos = self.GetCurrentPos()
        
        line = self.GetCurrentLine()
        col = self.GetColumn(caretPos)
        
        # print('update ui, line:', line, 'col:', col, 'pos', caretPos)
        
        word_start = self.WordStartPosition(caretPos, True)
        
        self.word_start = word_start
        self.cur_pos = caretPos
        self.word = self.GetRange(self.word_start, self.cur_pos).strip()
        self.line = line
        self.col = col
        self.cur_line_str = self.GetLineText(self.line)
        self.cur_line_indent = self.GetLineIndentation(self.line)
        self.cur_char = self.GetCharAt(self.cur_pos)
        
        # self.GetTopLevelParent().info(s2=f'行: {line+1} 列: {col+1}')
        
        ########
        # check bracket
        ########
        if caretPos > 0:
            charBefore = self.GetCharAt(caretPos - 1)
            styleBefore = self.GetStyleAt(caretPos - 1)

        # Check before.
        if charBefore and chr(charBefore) in '[]{}()' \
        and styleBefore == stc.STC_P_OPERATOR:
            braceAtCaret = caretPos - 1

        # Check after.
        if braceAtCaret < 0:
            charAfter = self.GetCharAt(caretPos)
            styleAfter = self.GetStyleAt(caretPos)
            if charAfter and chr(charAfter) in '[]{}()' \
            and styleAfter == stc.STC_P_OPERATOR:
                braceAtCaret = caretPos

        if braceAtCaret >= 0:
            braceOpposite = self.BraceMatch(braceAtCaret)

        if braceAtCaret != -1  and braceOpposite == -1:
            self.BraceBadLight(braceAtCaret)
        else:
            self.BraceHighlight(braceAtCaret, braceOpposite)  
            
        sel_start, sel_end, sel_start_pos, sel_end_pos = self.get_sel_line()   
        self.sel_start_pos = sel_start_pos
        self.sel_end_pos = sel_end_pos
    
    # highlight_Line, one-based index
    def highlight_line(self, line):
        self.MarkerDeleteAll(1)
        self.MarkerAdd(line-1, 1)
    
    def OnKeyPressed(self, event):  
        if self.AutoCompActive(): return event.Skip()
        self.last_key = event.GetKeyCode()
        print('press:', self.last_key)

        self.code.saved = False
        self.fixed = True
        
        # press enter
        if self.last_key == 13:
            self.AddText('\n')
            if self.line>=0:
                last_line = self.cur_line_str
                last_indent = self.cur_line_indent
                if len(last_line):
                    indent_add = 4*(last_line[-1]==':')
                else:
                    indent_add = 0
                self.WriteText(' '*(last_indent+indent_add))
            
            return 
        # press F1
        if self.last_key  == 340:
            docs = get_jedi_doc(self.GetValue(), self.line+1, self.col)
            # print('docstring', docs)
            self.CallTipShow(self.cur_pos, docs[0].docstring())    
                     
        # Ctrl + /, comment
        elif self.last_key == 47 and event.ControlDown():
            return self.comment()
            
        event.Skip()

    def on_text_changed(self, event):
        
        key = event.GetKey()
        ############
        # auto-comp 
        ############
        # space, enter, (
        '''
        if (self.cur_pos - self.word_start >= 1) and key not in  [32, 10, 13, 40]:
            comps = get_jedi_comp(self.GetValue(), self.line+1, self.col+1)
            comps = [ cm for cm in comps if len(cm) > 0 ]
            if len(comps) > 0:
                self.AutoCompShow(0, " ".join(comps))
        '''
        
        # . press
        # if self.col > 0 and not key in {10,13} and not self.AutoCompActive() and (
        #     key == 46 or (self.cur_pos - self.word_start >= 2)):
        '''
        if self.col > 0 and not key in {10,13} and not self.AutoCompActive() and key==46:
                comps = get_jedi_comp(self.GetValue(), self.line+1, self.col+1)
                comps = [ cm for cm in comps if len(cm) > 0 ]
                if len(comps) > 0: self.AutoCompShow(0, " ".join(comps))
        '''
        def find_expr(cont):
            buf, cur = [], 0
            for i in cont:
                buf.insert(0, i)
                if i in ')]}': cur += 1
                if i in '{[(': cur -= 1
                if i=='\n' or cur<=0 and i==' ':
                    return ''.join(buf).strip()
                
        if key==46:
            cont = self.GetValue()
            expr = find_expr(cont[:self.cur_pos][::-1])
            self.laxer.parse(cont)
            #print(self.line, word, '===')
            
            comps = self.laxer.list(self.line, expr)
            if len(comps)==0: return
            # comps = [i[0].split('.')[-1] for i in comps]
            self.AutoCompShow(0, ' '.join(comps))
            
            
    
    def comment(self, com=None):
        for i in range(10): self.Tab()
        start, end = self.GetSelection()
        cont = self.GetSelectedText()
        iscomment = cont.strip()[0]=='#'
        if not iscomment and com!=False:
            rptxt = cont.replace(' '*40, '# ')
            self.ReplaceSelection(rptxt)
        elif iscomment and com!=True:
            rptxt = cont.replace(' '*40+'# ', '')
            self.ReplaceSelection(rptxt)
        else:
            rptxt = cont.replace(' '*40, '')
            self.ReplaceSelection(rptxt)
        self.SetSelection(start, start+len(rptxt))
    
    def search_next(self, pattern):
        if self.sel_end_pos > self.sel_start_pos:
            print('has selection!')
            self.CharRight()
        self.SearchAnchor()
        flag = stc.STC_FIND_MATCHCASE
        pos = self.SearchNext(flag, pattern)
        if pos>0:
            self.GotoPos(pos)
            for i in range(len(pattern)):
                self.CharRightExtend()
        else: self.DocumentEnd()
            
        print('search:', pos)
        
    def search_prev(self, pattern):
        if self.sel_end_pos > self.sel_start_pos:
            print('has selection!')
            for i in range(len(pattern)):
                self.CharLeft()
        self.SearchAnchor()
        flag = stc.STC_FIND_MATCHCASE
        pos = self.SearchPrev(flag, pattern)
        if pos>0:
            self.GotoPos(pos)
            for i in range(len(pattern)):
                self.CharRightExtend()
        else:
            self.DocumentStart()
            
        print('search:', pos)
       
       
    def OnMarginClick(self, event):
        pos = event.GetPosition()
        rxy = self.PositionToXY(pos)
        ret, col, line = rxy
        line = ret *(line + 1)
        if line > 0:
            if line in self.breakpoints:
                self.breakpoints.remove(line)
                self.MarkerDelete(line-1, 0)
            else:
                self.breakpoints.append(line)
                self.MarkerAdd(line-1, 0)
        from .plugins.code_plugin import BreakPoint
        BreakPoint().start(self.GetTopLevelParent())

    
    def on_autocomp_finish(self, event):
        if self.GetCharAt(self.word_start-1)!=46:
            self.Remove(self.word_start, self.cur_pos)

    def set_code(self, code):
        self.code = code
        self.SetValue(code.cont)

    @property
    def object(self): return self.code

    @object.setter
    def object(self, obj): self.set_code(obj)
    

    @property
    def name(self):
        return os.path.split(self.code.name)[-1]

    def Save(self):
        if self.code.saved: return
        if self.code.path is None:
            filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in ['py']])
            dialog = wx.FileDialog(self, '保存', '', self.code.name, filt, wx.FD_SAVE)
            if dialog.ShowModal() != wx.ID_OK: return dialog.Destroy()
            self.code.path = dialog.GetPath()
            self.code.name = os.path.basename(self.code.path)
            dialog.Destroy()
        # self.SaveFile(self.code.path)
        with open(self.code.path, 'w', encoding='utf-8') as f:
            f.write(self.GetValue())
        self.code.saved = True

    def OnClose(self, event):
        if not self.code.saved:
            dialog = wx.MessageDialog(self, '关闭前是否保存?', '保存', wx.YES_NO | wx.CANCEL)
            rst = dialog.ShowModal()
            dialog.Destroy()
            if rst == wx.ID_YES: self.Save()
            if rst == wx.ID_CANCEL:
                event.Veto()

if __name__ == '__main__':
    app = wx.App()
    frame = wx.Frame(None)
    pad = CodePad(frame)
    pad.SetValue("""
import numpy as np
class A:
    def __init__(self, a, b):
        self.a = a
        self.b = b
        
a = A()
a.
""")
    pad.highlight_line(3)
    pad.highlight_line(5)
    frame.Show()
    app.MainLoop()
